<template>
  <div class="json-viewer">
    <div class="json-viewer__controls">
      <a-button type="primary" size="small" @click="success">复制</a-button>
      <a-checkbox v-model:checked="isCompress">压缩</a-checkbox>
      <a-checkbox v-model:checked="isEscaped">转义</a-checkbox>
    </div>
    <pre class="json-viewer__content">
      <div v-for="(line, index) in jsonLines" :key="index" class="json-viewer__line">
          <span class="json-viewer__line-number">{{ index + 1 }}</span>
          <span class="json-viewer__line-content" v-html="line"></span>
      </div>
    </pre>
  </div>
</template>

<script>
import { ref } from 'vue'
import { message } from 'ant-design-vue'

export default {
  props: {
    json: {
      type: [Object, Array, String],
      required: true
    }
  },
  setup() {
    const collapsedBlocks = ref(new Set())
    const jsonFormatDateStr = ref('{}')

    const isBlockCollapsed = (level) => {
      return collapsedBlocks.value.has(level)
    }

    const updateCollapsedState = (level, isBlockCollapsed) => {
      if (isBlockCollapsed) {
        collapsedBlocks.value.delete(level)
      } else {
        collapsedBlocks.value.add(level)
      }
    }
    const success = () => {
      navigator.clipboard
        .writeText(jsonFormatDateStr.value)
        .then(() => {
          message.success('已复制到粘贴板!', 3)
        })
        .catch((error) => {
          console.error('复制失败:', error)
          message.error('复制失败，请重试', 3)
        })
    }

    return {
      success,
      collapsedBlocks,
      jsonFormatDateStr,
      isBlockCollapsed,
      updateCollapsedState
    }
  },
  data() {
    return {
      isCompress: false,
      isEscaped: false,
      collapsedMinLevel: 4
    }
  },
  computed: {
    processedJson() {
      let json = this.json
      if (!json) {
        json = {}
      }
      try {
        if (typeof json === 'string') {
          json = JSON.parse(json)
        }
        json = JSON.stringify(json, null, this.isCompress ? 0 : 2)
        if (this.isEscaped) {
          json = this.escapeJson(json)
        } else {
          json = this.unescapeJson(json)
        }
      } catch (e) {
        console.error('Invalid JSON string:', e)
        return e.message
      }
      this.jsonFormatDateStr = json
      return json
    },
    jsonLines() {
      return this.highlightJson(this.processedJson)
    }
  },
  methods: {
    escapeJson(json) {
      if (typeof json === 'string') {
        return json.replace(/"/g, '\\"')
      }
      return json
    },
    unescapeJson(json) {
      if (typeof json === 'string') {
        return json.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/\\"/g, '"')
      }
      return json
    },
    highlightJson(jsonStr) {
      //如果是空的 增加一个默认值
      if (this.collapsedBlocks.size === 0 && this.collapsedMinLevel > 0) {
        this.collapsedBlocks.add(this.collapsedMinLevel)
      }
      // 将JSON字符串按行分割
      const jsonLines = jsonStr.split('\n')
      const highlightedLines = []
      let currentLevel = 0
      jsonLines.forEach((line, index) => {
        if (line.includes('{') || line.includes('[')) {
          currentLevel++
          const isCollapsed = this.isBlockCollapsed(currentLevel)
          if (currentLevel > this.collapsedMinLevel) {
            return
          }

          // 生成折叠按钮
          const buttonContent = isCollapsed ? '+' : '-'
          const buttonColor = isCollapsed ? '#00cfe8' : '#ea5455'
          let lineHtml = this.highlightLine(line)
          if (currentLevel > 1) {
            lineHtml =
              lineHtml +
              `<div class="braceButton" data-block-level=${currentLevel} style="color: ${buttonColor};">${buttonContent}</div>`
          }

          highlightedLines.push(lineHtml)
        } else if (line.trim() === '}' || line.trim() === ']') {
          currentLevel--
          if (currentLevel >= this.collapsedMinLevel) {
            return
          }
          highlightedLines.push(`<span class="brace">${line}</span>`)
        } else {
          if (currentLevel >= this.collapsedMinLevel) {
            return
          }
          highlightedLines.push(this.highlightLine(line))
        }
      })
      return highlightedLines;
    },
    highlightLine(line) {
      line = line
        .replace(/"([^"]+)":/g, '"<span class="key" contenteditable="true">$1</span>":')
        .replace(
          /"([^"]+)"(?=[,}\n]|$)/g,
          `<span class="string" contenteditable="true">$1</span>`
        )
        .replace(
          /(\d+(\.\d+)?)(?=[,}\n]|$)/g,
          '<span class="number" contenteditable="true">$1</span>'
        )
        .replace(
          /(true|false)(?=[,}\n]|$)/g,
          '<span class="boolean" contenteditable="true">$1</span>'
        )
        .replace(/null(?=[,}\n]|$)/g, '<span class="null" contenteditable="true">null</span>')
      return `<span class="brace">${line}</span>`
    },
    toggleBlock(event) {
      let blockKey = event.target.getAttribute('data-block-level')
      if (!blockKey) {
        return
      }
      blockKey = parseInt(blockKey)
      const isCollapsed = this.isBlockCollapsed(blockKey)
      this.updateCollapsedState(blockKey, isCollapsed)
      if (this.collapsedBlocks.size > 0) {
        this.collapsedMinLevel = Array.from(this.collapsedBlocks).reduce((a, b) => Math.min(a, b))
      } else {
        this.collapsedMinLevel = 99999
      }
    }
  },
  mounted() {
    document.addEventListener('click', this.toggleBlock)
  },
  beforeDestroy() {
    document.removeEventListener('click', this.toggleBlock)
  }
}
</script>

<style>
.json-viewer {
  font-family: monospace;
  background-color: #f5f5f5;
  border: 1px solid #ddd;
  height: 100%;
  width: 100%;
  overflow-y: auto;
}

.json-viewer__controls {
  height: 50px;
  width: 100%;
  padding-left: 20px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  background-color: white;
  gap: 20px;
}

.json-viewer__content {
  margin: 0;
  padding-left: 10px;
  align-items: center;
  white-space: pre-wrap;
}

.braceButton {
  width: 24px;
  height: 24px;
  cursor: pointer;
  border: 1px solid #e88384;
  border-radius: 20%;
  font-size: 22px;
  font-weight: bold;
  line-height: 22px;
  text-align: center;
  margin-left: 8px;
}

.braceButton:hover {
  background-color: #c2e4e4;
}

.json-viewer__line {
  display: flex;
  align-items: center;
}

.json-viewer__line-number {
  color: #888;
  width: 40px;
  user-select: none;
}

.json-viewer__line-content {
  flex: 1;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.json-viewer__line-content .string {
  cursor: pointer;
  color: #3ab54a;
}

.json-viewer__line-content .number {
  cursor: pointer;
  color: #25aae2;
}

.json-viewer__line-content .boolean {
  cursor: pointer;
  color: #f98280;
}

.json-viewer__line-content .null {
  cursor: pointer;
  color: #f1592a;
}

.json-viewer__line-content .key {
  cursor: pointer;
  color: #92278f;
}

.json-viewer__line-content .brace {
  color: #000;
}

</style>
